package com.egg.common.util;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSONObject;
import com.egg.common.core.exception.CustomException;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.UUID;


/**
 * 常见加密算法工具类
 */
@Slf4j
public class SecureUtil {

    // ---------- 一些辅助方法

    /**
     * 根据限定的每组字节长度，将字节数组分组
     */
    private static byte[][] splitBytes(byte[] bytes, int splitLength) {
        // bytes与splitLength的余数
        int remainder = bytes.length % splitLength;
        // 数据拆分后的组数，余数不为0时加1
        int quotient = remainder != 0 ? bytes.length / splitLength + 1 : bytes.length / splitLength;
        byte[][] arrays = new byte[quotient][];
        byte[] array = null;
        for (int i = 0; i < quotient; i++) {
            // 如果是最后一组（quotient-1）,同时余数不等于0，就将最后一组设置为remainder的长度
            if (i == quotient - 1 && remainder != 0) {
                array = new byte[remainder];
                System.arraycopy(bytes, i * splitLength, array, 0, remainder);
            } else {
                array = new byte[splitLength];
                System.arraycopy(bytes, i * splitLength, array, 0, splitLength);
            }
            arrays[i] = array;
        }
        return arrays;
    }

    /**
     * 将字节数组转换成16进制字符串
     */
    private static String bytesToHexString(byte[] bytes) {
        StringBuffer sb = new StringBuffer(bytes.length);
        String temp = null;
        for (int i = 0; i < bytes.length; i++) {
            temp = Integer.toHexString(0xFF & bytes[i]);
            if (temp.length() < 2) {
                sb.append(0);
            }
            sb.append(temp);
        }
        return sb.toString();
    }

    /**
     * 将16进制字符串转换成字节数组
     */
    private static byte[] hexStringToBytes(String hex) {

        int len = (hex.length() / 2);
        hex = hex.toUpperCase();
        byte[] result = new byte[len];
        char[] chars = hex.toCharArray();
        for (int i = 0; i < len; i++) {
            int pos = i * 2;
            result[i] = (byte) (toByte(chars[pos]) << 4 | toByte(chars[pos + 1]));
        }
        return result;
    }

    /**
     * 将char转换为byte
     */
    private static byte toByte(char c) {
        return (byte) "0123456789ABCDEF".indexOf(c);
    }


    // ----------------------- 摘要加密 -----------------------

    /**
     * md5加密
     *
     * @param str 指定字符串
     * @return 加密后的字符串
     */
    public static String md5(String str) {
        str = (str == null ? "" : str);
        char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
        try {
            byte[] btInput = str.getBytes();
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            mdInst.update(btInput);
            byte[] md = mdInst.digest();
            int j = md.length;
            char[] strA = new char[j * 2];
            int k = 0;
            for (byte byte0 : md) {
                strA[k++] = hexDigits[byte0 >>> 4 & 0xf];
                strA[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(strA);
        } catch (Exception e) {
            log.error("md5加密失败 ", e);
            throw new CustomException("md5加密失败");
        }
    }

    /**
     * md5加盐加密: md5(md5(str) + md5(salt))
     *
     * @param str  字符串
     * @param salt 盐
     * @return 加密后的字符串
     */
    public static String md5BySalt(String str, String salt) {
        return md5(md5(str) + md5(salt));
    }

    /**
     * sha1加密
     *
     * @param str 指定字符串
     * @return 加密后的字符串
     */
    public static String sha1(String str) {
        try {
            str = (str == null ? "" : str);
            MessageDigest md = MessageDigest.getInstance("SHA1");
            byte[] b = str.getBytes();
            md.update(b);
            byte[] b2 = md.digest();
            int len = b2.length;
            String strA = "0123456789abcdef";
            char[] ch = strA.toCharArray();
            char[] chs = new char[len * 2];
            for (int i = 0, k = 0; i < len; i++) {
                byte b3 = b2[i];
                chs[k++] = ch[b3 >>> 4 & 0xf];
                chs[k++] = ch[b3 & 0xf];
            }
            return new String(chs);
        } catch (Exception e) {
            log.error("sha1加密失败 ", e);
            throw new CustomException("sha1加密失败");
        }
    }

    /**
     * sha256加密
     *
     * @param str 指定字符串
     * @return 加密后的字符串
     */
    public static String sha256(String str) {
        try {
            str = (str == null ? "" : str);
            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.update(str.getBytes("UTF-8"));

            byte[] bytes = messageDigest.digest();
            StringBuilder builder = new StringBuilder();
            String temp;
            for (int i = 0; i < bytes.length; i++) {
                temp = Integer.toHexString(bytes[i] & 0xFF);
                if (temp.length() == 1) {
                    builder.append("0");
                }
                builder.append(temp);
            }

            return builder.toString();
        } catch (Exception e) {
            log.error("sha256加密失败 ", e);
            throw new CustomException("sha256加密失败");
        }
    }


    // ----------------------- 对称加密 AES -----------------------

    private static final String ASE_ALGORITHM = "AES";

    /**
     * 默认密码算法
     * --模式:ECB
     * --补码方式:PKCS5Padding
     */
    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";

    private static Base64.Encoder encoder = Base64.getEncoder();

    private static Base64.Decoder decoder = Base64.getDecoder();

    /**
     * 生成加密秘钥
     *
     * @param password 秘钥 【任意】
     * @return SecretKeySpec
     * @throws NoSuchAlgorithmException
     */
    private static SecretKeySpec getSecureRandomSecretKey(final String password) throws NoSuchAlgorithmException {
        KeyGenerator kg = KeyGenerator.getInstance(ASE_ALGORITHM);
        SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
        secureRandom.setSeed(password.getBytes());
        kg.init(128, secureRandom);
        SecretKey secretKey = kg.generateKey();
        return new SecretKeySpec(secretKey.getEncoded(), ASE_ALGORITHM);
    }

    /**
     * 生成加密秘钥
     *
     * @param password 秘钥 【16，32】
     * @return SecretKeySpec
     * @throws NoSuchAlgorithmException
     */
    private static SecretKeySpec getSecretKey(final String password) {
        return new SecretKeySpec(password.getBytes(), ASE_ALGORITHM);
    }

    /**
     * AES加密
     *
     * @param key  加密的密钥
     * @param text 需要加密的字符串
     * @return 返回Base64转码后的加密数据
     */
    public static String aesEncrypt(String key, String text) {
        if (ObjectUtil.isEmpty(key)) {
            return text;
        }
        try {
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(key));
            byte[] result = cipher.doFinal(text.getBytes("utf-8"));
            return encoder.encodeToString(result);
        } catch (Exception e) {
            log.error("AES加密失败 ", e);
            throw new RuntimeException("加密失败");
        }
    }

    /**
     * AES解密
     *
     * @param key  加密的密钥
     * @param text 已加密的密文
     * @return 返回解密后的数据
     */
    public static String aesDecrypt(String key, String text) {
        if (ObjectUtil.isEmpty(key)) {
            return text;
        }
        try {
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, getSecretKey(key));
            byte[] result = cipher.doFinal(decoder.decode(text));
            return new String(result, "utf-8");
        } catch (Exception e) {
            log.error("AES解密失败 ", e);
            throw new RuntimeException("解密失败");
        }
    }


    // ----------------------- 非对称加密 RSA -----------------------

    private static final String RSA_ALGORITHM = "RSA";

    private static final int RSA_KEY_SIZE = 1024;

    // RSA私钥KEY
    private static final String RSA_PRIVATE_KEY = "rsa_private";
    // RSA公钥KEY
    private static final String RSA_PUBLIC_KEY = "rsa_public";


    /**
     * 生成密钥对
     *
     * @return Map对象 (private=私钥, public=公钥)
     * @throws Exception 异常
     */
    public static HashMap<String, String> rsaGenerateKeyPair() {
        KeyPair keyPair;
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
            keyPairGenerator.initialize(
                    RSA_KEY_SIZE
                    , new SecureRandom(UUID.randomUUID().toString().replaceAll("-", "").getBytes())
            );
            keyPair = keyPairGenerator.generateKeyPair();
        } catch (Exception e) {
            return new HashMap<>();
        }
        RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
        HashMap<String, String> map = new HashMap<String, String>(16);
        map.put(RSA_PRIVATE_KEY, encoder.encodeToString(rsaPrivateKey.getEncoded()));
        map.put(RSA_PUBLIC_KEY, encoder.encodeToString(rsaPublicKey.getEncoded()));
        return map;
    }

    /**
     * 获取公钥
     *
     * @param rsaPair 密钥对
     * @return 公钥值
     */
    public static String rsaPublicKey(HashMap<String, String> rsaPair) {
        return rsaPair.getOrDefault(RSA_PUBLIC_KEY, "");
    }

    /**
     * 获取私钥
     *
     * @param rsaPair 密钥对
     * @return 私钥值
     */
    public static String rsaPrivateKey(HashMap<String, String> rsaPair) {
        return rsaPair.getOrDefault(RSA_PRIVATE_KEY, "");
    }

    /**
     * RSA公钥加密
     *
     * @param publicKeyString 公钥
     * @param content         内容
     * @return 加密后内容
     */
    public static String rsaEncryptByPublic(String publicKeyString, String content) {
        try {
            // 获得公钥对象
            PublicKey publicKey = getPublicKeyFromString(publicKeyString);
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            // 该密钥能够加密的最大字节长度
            int splitLength = ((RSAPublicKey) publicKey).getModulus().bitLength() / 8 - 11;
            byte[][] arrays = splitBytes(content.getBytes(), splitLength);
            StringBuilder stringBuffer = new StringBuilder();
            for (byte[] array : arrays) {
                stringBuffer.append(bytesToHexString(cipher.doFinal(array)));
            }
            return stringBuffer.toString();
        } catch (Exception e) {
            log.error("RSA公钥加密失败 ", e);
            throw new CustomException("RSA公钥加密失败");
        }
    }

    /**
     * RSA私钥解密
     *
     * @param privateKeyString 公钥
     * @param content          已加密内容
     * @return 解密后内容
     */
    public static String rsaDecryptByPrivate(String privateKeyString, String content) {
        try {
            PrivateKey privateKey = getPrivateKeyFromString(privateKeyString);
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            // 该密钥能够加密的最大字节长度
            int splitLength = ((RSAPrivateKey) privateKey).getModulus().bitLength() / 8;
            byte[] contentBytes = hexStringToBytes(content);
            byte[][] arrays = splitBytes(contentBytes, splitLength);
            StringBuilder stringBuffer = new StringBuilder();
            for (byte[] array : arrays) {
                stringBuffer.append(new String(cipher.doFinal(array)));
            }
            return stringBuffer.toString();
        } catch (Exception e) {
            log.error("RSA私钥解密失败 ", e);
            throw new CustomException("RSA私钥解密失败");
        }
    }

    // ---------- 获取公私密钥

    /**
     * 根据公钥字符串获取 公钥对象
     */
    private static PublicKey getPublicKeyFromString(String key) throws NoSuchAlgorithmException, InvalidKeySpecException {
        // 过滤掉\r\n
        key = key.replace("\r\n", "");
        // 取得公钥
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(decoder.decode(key));
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
        return publicKey;
    }

    /**
     * 根据私钥字符串获取 私钥对象
     */
    private static PrivateKey getPrivateKeyFromString(String key) throws NoSuchAlgorithmException, InvalidKeySpecException {
        // 过滤掉\r\n
        key = key.replace("\r\n", "");
        // 取得私钥
        PKCS8EncodedKeySpec x509KeySpec = new PKCS8EncodedKeySpec(decoder.decode(key));
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        PrivateKey privateKey = keyFactory.generatePrivate(x509KeySpec);
        return privateKey;
    }


    public static void main(String[] args) throws Exception {
        System.out.println("第一阶段开始...");
        // 第一步, 生成公私钥
        HashMap<String, String> pairHashMap = rsaGenerateKeyPair();
        System.out.println("后端生成临时公私秘钥对: " + pairHashMap.toString());
        // 第二步, 将 pairHashMap 放入缓存
        // 第三步, 返回公钥给前端
        String publicKey = rsaPublicKey(pairHashMap);
        System.out.println("前端获取临时公钥: " + publicKey);
        // 第四步, 前端生成对称秘钥
        String aes = RandomUtil.randomString(32);
        System.out.println("前端生成对称秘钥原文: " + aes);
        // 第五步, 前端用公钥加密秘钥
        String aseEncrypt = rsaEncryptByPublic(publicKey, aes);
        System.out.println("前端用公钥加密对称秘钥密文: " + aseEncrypt);
        // 第六步, 后端获取私钥
        String privateKey = rsaPrivateKey(pairHashMap);
        System.out.println("后端用公钥获取私钥: " + privateKey);
        // 第七步, 后端用私钥解密对称加密密文获取对称加密秘钥
        String rsaDecrypt = rsaDecryptByPrivate(privateKey, aseEncrypt);
        System.out.println("后端用私钥解密对称秘钥密文: " + rsaDecrypt);
        // 第八步, 后端保存对称秘钥 rsaDecrypt
        System.out.println("后端保存对称秘钥明文...");

        System.out.println("第一阶段结束...");
        System.out.println("第二阶段开始...");

        // 第九步, 前端加密对称秘钥加密请求数据
        JSONObject sourceRequest = new JSONObject();
        sourceRequest.put("id", 10086);
        sourceRequest.put("name", "中国移动我不动");
        String sourceRequestStr = sourceRequest.toJSONString();
        System.out.println("前端生成请求明文: " + sourceRequestStr);
        String aesEncryptRequest = aesEncrypt(aes, sourceRequestStr);
        System.out.println("前端发送请求密文: " + aesEncryptRequest);

        // 第十步, 后端解密对称密文
        String aesDecryptRequest = aesDecrypt(aes, aesEncryptRequest);
        System.out.println("后端获取请求明文: " + aesDecryptRequest);

        // 第十一步, 后端返回加密密文
        JSONObject sourceResponse = new JSONObject();
        sourceResponse.put("id", 10010);
        sourceResponse.put("name", "中国联通我不通");
        String sourceResponseStr = sourceResponse.toJSONString();
        System.out.println("后端生成响应明文: " + sourceResponseStr);
        String aesEncryptResponse = aesEncrypt(aes, sourceResponseStr);
        System.out.println("后端生成响应密文: " + aesEncryptResponse);

        // 第十二步, 前端解析后端返回的密文
        String aesDecryptResponse = aesDecrypt(aes, aesEncryptResponse);
        System.out.println("前端解密响应密文: " + aesDecryptResponse);
        System.out.println("第二阶段结束...");
    }


}
